/*
 *  decode.c
 *
 *  Created on: 2010-5-9
 *      Author: liuyond
 */

#include "cpu.h"
#include "common.h"
#include <stddef.h>

static byte *fetch_ptr = NULL;
extern decode_t one_opcode_table[];
extern decode_t two_opcode_table[];

void resolve_address(cpu_t *cpu, instruction_t *i)
{
	switch (i->operand.rm)
	{
		case 0x0:
			i->operand.address = cpu_get_reg(cpu, BX) + cpu_get_reg(cpu, SI);
			break;
		case 0x1:
			i->operand.address = cpu_get_reg(cpu, BX) + cpu_get_reg(cpu, DI);
			break;
		case 0x2:
			i->operand.address = cpu_get_reg(cpu, SP) + cpu_get_reg(cpu, SI);
			break;
		case 0x3:
			i->operand.address = cpu_get_reg(cpu, BP) + cpu_get_reg(cpu, DI);
			break;
		case 0x4:
			i->operand.address = cpu_get_reg(cpu, SI);
			break;
		case 0x5:
			i->operand.address = cpu_get_reg(cpu, DI);
			break;
		case 0x6:
			i->operand.address = i->operand.disp;
			break;
		case 0x7:
			i->operand.address = cpu_get_reg(cpu, BX);
			break;
	}
}
void resolve_address_disp(cpu_t *cpu, instruction_t *i)
{
	switch (i->operand.rm)
	{
		case 0x0:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, BX) + cpu_get_reg(cpu, SI);
			break;
		case 0x1:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, BX) + cpu_get_reg(cpu, DI);
			break;
		case 0x2:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, SP) + cpu_get_reg(cpu, SI);
			break;
		case 0x3:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, BP) + cpu_get_reg(cpu, DI);
			break;
		case 0x4:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, SI);
			break;
		case 0x5:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, DI);
			break;
		case 0x6:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, BP);
			break;
		case 0x7:
			i->operand.address = i->operand.disp;
			i->operand.address += cpu_get_reg(cpu, BX);
			break;
	}
}

/**
 * resolve_modrm
 */
int resolve_modrm(cpu_t *cpu, instruction_t *instruction)
{
	switch (instruction->operand.mod)
	{
		case 0x0: // mod = 00
			if (instruction->operand.rm == 0x6)
			{
				instruction->operand.disp = fetch_word(fetch_ptr);
				fetch_ptr += sizeof(word);
			}
			resolve_address(cpu, instruction);
			break;
		case 0x1: // mod = 01
			instruction->operand.disp = fetch_byte(fetch_ptr++);
			resolve_address_disp(cpu, instruction);
			break;
		case 0x2: // mod = 10
			instruction->operand.disp = fetch_word(fetch_ptr);
			fetch_ptr += sizeof(word);
			resolve_address_disp(cpu, instruction);
			break;
		case 0x3: // mod = 11
			return 0;
	}
	return 0;
}
/**
 * parse_modrm_byte
 */
int parse_modrm_byte(cpu_t *cpu, instruction_t *instruction)
{
	unsigned mod, rm, reg;
	byte temp = *fetch_ptr++;

	mod = (temp >> 6) & 0x3;
	reg = (temp >> 3) & 0x7;
	rm  = (temp & 0x7);
	instruction->operand.mod = mod;
	instruction->operand.reg = reg;
	instruction->operand.rm  = rm;
	if (mod != 0x3) // operand need to access memory
	{
		resolve_modrm(cpu, instruction);
	}

	return 0;
}
/**
 * is_prefix - judge if @code is a prefix of 8086 instruction
 * @code:   instruction's opcode
 * @return: return 1 if @code is a prefix of 8086 instruction, otherwise return 0
 */
int is_prefix(byte code)
{
	int  i;
	byte prefixs[] = { 0x2E, 0x3E, 0x26, 0x36, 0xF0, 0xF2, 0xF3 };
	for (i = 0; i < sizeof(prefixs); i++)
	{
		if (code == prefixs[i]) return 1;
	}
	return 0;
}
/**
 * need_modrm -
 * @return:
 */
int need_modrm(unsigned long action)
{
	return (action & HAS_MODRM);
}

/**
 * need_immediate
 */
int need_immediate(unsigned long action)
{
	return (action & HAS_IMM);
}

/**
 * fetch_and_decode - fetch and decode a instruction
 * @cpu:
 * @instruction: instruction pointer
 * @return: return 0 if succeed, otherwise return -1
 */
int fetch_and_decode(cpu_t *cpu, instruction_t *instruction)
{
	byte temp;
	unsigned long decode_action;

	// 1. parse prefix
	fetch_ptr = BYTEP(&cpu->ram[cpu->ip]);
	temp = *fetch_ptr++;
	while (is_prefix(temp))
	{
		switch (temp)
		{
			case 0x2E: // CS
				instruction->operand.seg = CS;
				break;
			case 0x3E: // DS
				instruction->operand.seg = DS;
				break;
			case 0x26: // ES
				instruction->operand.seg = ES;
				break;
			case 0x36: // SS
				instruction->operand.seg = SS;
				break;
			case 0xF0: // LOCK
				break;
			case 0xF2: // REPNE/REPNZ
				instruction->repeatable = 2;
				break;
			case 0xF3: // REP/REPE/REPZ
				instruction->repeatable = 3;
				break;
			default:
				return -1;
		}
		temp = *fetch_ptr++;
	}

	// 2. parse opcode
	instruction->opcode = temp;
	if (instruction->opcode == 0x0F)
	{
		instruction->opcode = 0x100 | *fetch_ptr++;
	}

	// 3. Lookup table to find action and function pointer
	if (!(instruction->opcode & 0x100)) // one byte instruction
	{
		decode_action = one_opcode_table[instruction->opcode].action;
		instruction->execute = one_opcode_table[instruction->opcode].executor;
	}
	else // two byte instruction
	{
		instruction->opcode &= 0xFF;
		decode_action = two_opcode_table[instruction->opcode].action;
		instruction->execute = two_opcode_table[instruction->opcode].executor;
	}

	// 4. Operand resolution
	if (need_modrm(decode_action))
	{
		parse_modrm_byte(cpu, instruction);
	}
	if (need_immediate(decode_action))
	{
		decode_action &= HAS_IMM;
		if (decode_action == IMM_BYTE)
		{
			instruction->operand.imm = fetch_byte(fetch_ptr++);
		}
		else if (decode_action == GRP_BYTE
				&& instruction->operand.reg < 2)
		{
			instruction->operand.imm = fetch_byte(fetch_ptr++);
		}
		else if (decode_action == IMM_WORD)
		{
			instruction->operand.imm = fetch_word(fetch_ptr);
			fetch_ptr += sizeof(word);
		}
		else if (decode_action == GRP_WORD
				&& instruction->operand.reg < 2)
		{
			instruction->operand.imm = fetch_word(fetch_ptr);
			fetch_ptr += sizeof(word);
		}
		else if (decode_action == IMM_DWORD)
		{
			instruction->operand.imm = fetch_dword(fetch_ptr);
			fetch_ptr += sizeof(dword);
		}
	}
	// 5. modify instruction pointer
	cpu->ip = WORD(fetch_ptr - cpu->ram);

	return 0;
}
